package dst.graph;

import dataStructures.list.SinglelyLinkedList;
import java.util.Iterator;

/**
 * The GraphNode class.
 * 
 * Fully Tested: Yes
 * Fully Documented: Yes
 * Created: 2011
 * Last Modified: Nov 16, 2011
 *
 * @author Shimu Wu
 * @param <T>
 */
public class GraphNode<T> implements Iterable<GraphNode<T>>,
        Comparable<GraphNode<T>>, Graphable<T>, GraphSearchable<T> {

    // ======================== PRIVATE FIELDS ========================
    /** This GraphNode's parent */
    //private GraphNode<T> parent;
    /** A List of children nodes */
    private SinglelyLinkedList<GraphNode<T>> children;

    /** The data stored in this GraphNode */
    private T data;
    
    /** A search id used for tree searching */
    private int color;
    
    private GraphSearchable discoverer;
    
    // ======================== PROTECTED FIELDS ========================
    /** The number of times this GraphNode has been returned by a graph
     * traversal (such as breadth-first search or depth-first search) */
    protected int returnCount = 0;

    /** The number of times this GraphNode has been visited by a graph
     * traversal (such as breadth-first search or depth-first search) */
    protected int exploreCount = 0;


    /** A color value used for tree searching */
    //protected byte color = 0;
    /**
     * Instantiates a GraphNode storing the given data. The GraphNode
     * has an empty list of children nodes.
     *
     * @param data the data to be stored by this GraphNode
     */
    public GraphNode(T data) {
        this.data = data;
        this.children = new SinglelyLinkedList<GraphNode<T>>();
    }

    /**
     * Returns true if this GraphNode's data is equal to the given data,
     * false otherwise. Comparison is done by calling the T.equals(Object)
     * method.
     * 
     * @param data
     * @return
     */
    public boolean hasData(T data) {
        return this.data.equals(data);
    }

    /**
     * Returns true if this GraphNode has a child with the given data.
     * False otherwise.
     *
     * @param data
     * @return
     */
    public boolean hasChild(T data) {
        for (GraphNodeInterface<T> child : children) {
            if (child.getData().equals(data)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns an Iterator for iterating over this GraphNode's children.
     * @return an Iterator for iterating over this GraphNode's children.
     */
    @Override
    public Iterator<GraphNode<T>> iterator() {
        return children.iterator();
    }

    /**
     * Returns the total number of children in this GraphNode
     * 
     * @return the total number of children in this GraphNode.
     */
    public int getTotalChildren() {
        return this.children.getSize();
    }

    /**
     * Returns the String representation of this GraphNode, in the following
     * format: <br />
     * GraphNode: (data of this GraphNode)
     * 
     * @return the String representation of this GraphNode
     */
    @Override
    public String toString() {
        return "TreeNode: " + data;
    }

    /**
     * Compares two GraphNodes by comparing which has the higher returnCount
     * (ie the number of times they have been returned by a graph
     * traversal). If this GraphNode has the higher returnCount, return 1,
     * if this GraphNode has the lower returnCount, return -1, if both
     * have the same returnCount, return -1 if this GraphNode's exploreCount
     * is less than the given node, return 1 if this GraphNode's exploreCount
     * is greater than the given node. Returns 0 if the returnCount and
     * the exploreCount are both equal.
     * 
     * @param o
     * @return
     */
    public int compareTo(GraphNode<T> o) {
        if (this.returnCount < o.returnCount) {
            return -1;
        } else if (this.returnCount > o.returnCount) {
            return 1;
        } else if (this.exploreCount < o.returnCount) {
            return -1;
        } else if (this.exploreCount > o.returnCount) {
            return 1;
        } else {
            return 0;
        }
    }

    // =========================== GETTERS/SETTERS ===========================
    @Override
    public T getData() {
        return this.data;
    }

    @Override
    public void setData(T newData) {
        this.data = newData;
    }

    @Override
    public void setColor(int color) {
        this.color = color;
    }

    @Override
    public int getColor() {
        return this.color;
    }

    /**
     * Returns the child with the given data. Returns null if no child
     * with the given data is found.
     * @param data
     * @return
     */
    public GraphNode<T> getNeighbour(T data) {
        for (GraphNode<T> child : children) {
            if (child.hasData(data)) {
                return child;
            }
        }
        return null;
    }

    @Override
    public Iterator<GraphNode<T>> getNeighbours() {
        return this.iterator();
    }

    // ====================== Adding/Removing Neighbours ======================
    /**
     * Add a child with the given data to this GraphNode. The added child
     * is also returned. If a child with the given data already exists,
     * then a child is not added, and the child with the given data is
     * returned.
     *
     * @param data the data to be stored by the child
     * @return the new child added to this GraphNode, if a child already
     * exists with the given data, then that child is returned instead
     */
    @Override
    public GraphNode<T> addNeighbour(T data) {
        // Get the child with the given data
        GraphNode<T> returnNeighbour = this.getNeighbour(data);
        if (returnNeighbour != null) {
            return returnNeighbour;
        } else {
            GraphNode<T> neighbour = new GraphNode<T>(data);
            this.children.addLast(neighbour);
            return neighbour;
        }
    }

    public void addNeighbour(GraphNode<T> neighbour) {
        this.children.addLast(neighbour);
    }

    @Override
    public GraphNode<T> removeNeighbour(T data) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public GraphSearchable<T> getDiscoverer() {
        return this.discoverer;
    }

    public void setDiscoverer(GraphSearchable<T> discoverer) {
        this.discoverer = discoverer;
    }
}
